/*
 * Copyright (c) 2011 Sultan Rehman
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package net.rehman.fbauth.filter;

import javax.servlet.*;
import java.util.*;
import java.io.*;
import javax.servlet.http.HttpServletRequest;
import net.rehman.fbauth.FbAuthHelper;
import org.apache.log4j.*;

/**
 * Filter to provide Facebook session identification and authorization.
 *
 * Note: Only tested to work with IFRAME canvas type Facebook apps
 *
 * @author sultanrehman at gmail.com
 */
public class FbAuthFilter implements Filter {

     public static final String SESSION_ATTR_SIGNED_REQUEST = "net.rehman.fbauth.signed_request";

     private static final Logger LOG = LogManager.getLogger( FbAuthFilter.class );
     private static final String INIT_PARAM_APP_EXT_PERMS = "appExtPerms";
     private static final String INIT_PARAM_APP_ID = "appId";
     private static final String INIT_PARAM_APP_SECRET = "appSecret";
     private static final String INIT_PARAM_APP_URL = "appUrl";
    
     private FilterConfig filterConfig = null;
     private String appId;
     private String appSecret;
     private String appUrl;
     private String appExtPerms;
     private FbAuthHelper fbAuthHelper;

     public void init( final FilterConfig filterConfig ) throws ServletException {

        this.filterConfig = filterConfig;

        appId = filterConfig.getInitParameter( INIT_PARAM_APP_ID );
        if ( appId == null || appId.length() == 0 ) {
            throw new ServletException( "Init parameter 'appId' must be set" );
        }
        appSecret = filterConfig.getInitParameter( INIT_PARAM_APP_SECRET );
        if ( appSecret == null || appSecret.length() == 0 ) {
            throw new ServletException( "Init parameter 'appSecret' must be set" );
        }
        appUrl = filterConfig.getInitParameter( INIT_PARAM_APP_URL );
        if ( appUrl == null || appUrl.length() == 0 ) {
            throw new ServletException( "Init parameter 'appUrl' must be set" );
        }
        appExtPerms = filterConfig.getInitParameter( INIT_PARAM_APP_EXT_PERMS );

        fbAuthHelper = new FbAuthHelper( appId, appSecret, appExtPerms );

        if ( LOG.isDebugEnabled() ) {
            LOG.debug( "FbAuthFilter initialized: appId=" + appId + ", appSecret=" + appSecret + ", appUrl=" + appUrl + ", appExtPerms=" + appExtPerms );
        }
     }

     public void destroy() {
        this.filterConfig = null;
     }

     public void doFilter( final ServletRequest request, final ServletResponse response, final FilterChain chain ) throws IOException, ServletException {
        Map< String, Object > sigreq = getSignedRequest( (HttpServletRequest)request );
        if ( sigreq == null || !fbAuthHelper.isAuthorized( sigreq ) ) {
            String redirectUrl = buildRedirectUrl( request );
            if ( LOG.isDebugEnabled() ) {
                LOG.debug( "Application not installed/authorized: Redirecting to '" + redirectUrl + "'" );
            }
            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            fbAuthHelper.writeAuthRedirectHtml( out, redirectUrl );
        }
        else {
            if ( LOG.isDebugEnabled() ) {
                LOG.debug( "HTTP parameter 'signed_request' found: parsed signed_request=" + sigreq );
            }
            chain.doFilter( request, response );
        }
    }

    private String buildRedirectUrl( final ServletRequest request ) {
        HttpServletRequest req = (HttpServletRequest) request;
        StringBuilder s = new StringBuilder(appUrl);
        String r = req.getServletPath();
        int i = appUrl.endsWith("/") && r.startsWith("/") ? 1 : 0;
        s.append(r, i, r.length());
        if (req.getQueryString() != null && req.getQueryString().length() > 0) {
            s.append('?').append(req.getQueryString());
        }
        return ( s.toString() );
    }

    /**
     * Retrieves Facebook 'signed_request' from HTTP stream if exists otherwise try to get it from session.
     */
    private Map<String, Object> getSignedRequest( final HttpServletRequest request ) throws IOException {
        Map<String, Object> sigreq = null;
        String rawsigreq = request.getParameter( "signed_request" );
        if ( rawsigreq == null ) {
            if ( LOG.isDebugEnabled() ) {
                LOG.debug( "'signed_request' not found in HTTP stream. Checking in session." );
            }
            sigreq = (HashMap<String, Object>)request.getSession().getAttribute( SESSION_ATTR_SIGNED_REQUEST );
            if ( sigreq != null ) {
                if ( LOG.isDebugEnabled() ) {
                    LOG.debug( "'signed_request' loaded from session." );
                }
            }
        }
        else {
            sigreq = fbAuthHelper.parseSignedRequest( rawsigreq );
            request.getSession().setAttribute( SESSION_ATTR_SIGNED_REQUEST, sigreq );
            if ( LOG.isDebugEnabled() ) {
                LOG.debug( "'signed_request' saved in session." );
            }
        }
        return ( sigreq );
    }
    
}
